#include <QFile>
#include <QHelpEvent>
#include <QToolTip>
#include <QAction>
#include <QPainter>
#include "Include/Lexers.h"
#include "TextEditor.h"
#include "Include/Settings.h"
#include "Include/Plugins.h"
#include "StatusBar.h"
#include "Include/Project.h"

namespace Plugins
{

TextEditor::TextEditor(QWidget *parent) :
    QsciScintilla(parent),
    _fileName(""),
    _bar(NULL),
    _project(NULL),
    allocatedIndicatorsOld(7)
{
    init("Default");
    setFocusPolicy(Qt::StrongFocus);

    QPixmap p(3, 17);
    p.fill(QColor(255,0,0));
    _errMarker = markerDefine(p);

    QPixmap p1(3, 17);
    p1.fill(QColor(255,255,0));
    _warnMarker = markerDefine(p1);

    QPixmap p2(3, 17);
    p2.fill(QColor(0,0,255));
    _convMarker = markerDefine(p2);


    QAction *act = new QAction(this);
    act->setShortcut(Qt::CTRL + Qt::Key_Space);
    connect(act, SIGNAL(triggered()), SLOT(onCodeComplite()));
    addAction(act);

    registerImage(1, QPixmap(":/images/attribute.png"));
    registerImage(2, QPixmap(":/images/class.png"));
    registerImage(3, QPixmap(":/images/method.png"));
    registerImage(4, QPixmap(":/images/module.png"));

    QToolTip::setFont (Settings::instance()->monoFont());
    setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
}

void TextEditor::init(const QString & lexName)
{
    PluginWrapper<Lexers::ILexers> lexers = loadPlugin<Lexers::ILexers>("liblexers.so");
    _lex = lexers->get(lexName, this);
    setLexer(_lex->lexer());

    connect(Plugins::Settings::instance(), SIGNAL(themeChanged()), SLOT(setTheme()));

    setIndentationGuides(true);
    setIndentationsUseTabs(false);
    setIndentationWidth(4);
    setAutoIndent(true);
    setUtf8(true);
    setEolMode(QsciScintilla::EolUnix);

    setMarginWidth(3, 40);
    setMarginLineNumbers(3, true);
    setMarginMarkerMask(3, 0);

    setMarginWidth(0, 3);
    setMarginLineNumbers(0, false);
    setMarginMarkerMask(0, (1 << _errMarker));

    setMarginWidth(1, 3);
    setMarginLineNumbers(1, false);
    setMarginMarkerMask(1, (1 << _warnMarker));

    setMarginWidth(2, 3);
    setMarginLineNumbers(2, false);
    setMarginMarkerMask(2, (1 << _convMarker));

    setBraceMatching(QsciScintilla::StrictBraceMatch);

    setAutoCompletionCaseSensitivity(true);
    setAutoCompletionReplaceWord(true);
    setAutoCompletionShowSingle(true);
    setAutoCompletionThreshold(2);

    setTheme();

    connect(this, SIGNAL(modificationChanged(bool)), SLOT(onModificationChanged(bool)));
    connect(this, SIGNAL(selectionChanged()), SLOT(onSelectionChanged()));
    connect(this, SIGNAL(cursorPositionChanged(int,int)), SLOT(onCursorPositionChanged(int, int)));
}

TextEditor::~TextEditor()
{
    //qDebug() << "Delete TextEditor";
}

QWidget * TextEditor::widget()
{
    return this;
}

bool TextEditor::load(const QString & path)
{
    _fileName = path;
    if (_project){
        QString displayName = QDir(_project->path()).relativeFilePath(path);
        _bar->setFileName(displayName);
    } else {
        _bar->setFileName(path);
    }
    loadDocument();
    return true;
}

bool TextEditor::save()
{
    QFile file(_fileName);
    if (file.open(QIODevice::WriteOnly)){
        file.write(text().toUtf8());
        file.flush();
        file.close();
        setModified(false);
        onSave();
        return true;
    }
    return false;
}

void TextEditor::setTheme(ITheme * theme, QFont * fnt)
{
    ITheme *set = theme;
    if (!set){
        set = Plugins::Settings::instance()->currentTheme();
        if (!set){
            set = Plugins::Settings::instance()->fallbackTheme();
        }
    }
    if (!fnt)
        lexer()->setFont(Plugins::Settings::instance()->font());
    else
        lexer()->setFont(*fnt);

    lexer()->setDefaultPaper(set->background());
    //lexer().setDefaultColor(set->foreground());

    set->setLexer(_lex->name());
    foreach(int style, _lex->styles()){
        lexer()->setColor(set->foreground(_lex->nameInTheme(style), _lex->color(style)), style);
        lexer()->setPaper(set->background(), style);
        QFont sfnt = lexer()->font(style);
        sfnt.setBold(set->bold(_lex->nameInTheme(style)));
        sfnt.setItalic(set->italic(_lex->nameInTheme(style)));
        lexer()->setFont(sfnt, style);
    }

    setMarginsForegroundColor(set->marginForeground());
    setMarginsBackgroundColor(set->marginBackground());

    setSelectionBackgroundColor(set->selectBackground());
    setSelectionForegroundColor(set->selectForeground());

    setMatchedBraceForegroundColor(QColor(0, 255, 0));
    setMatchedBraceBackgroundColor(set->background());
    setUnmatchedBraceForegroundColor(QColor(255, 0, 0));
    setUnmatchedBraceBackgroundColor(set->background());


    QColor back = set->background();
    setCaretForegroundColor(QColor(255 - back.red(), 255 - back.green(), 255 - back.blue()));
}

void TextEditor::loadDocument()
{
    QFile file(_fileName);
    qDebug() << "Open" << _fileName;
    if (file.open(QIODevice::ReadOnly)){
        setText(file.readAll());
        file.close();
    }
    onLoad();
}

QsciLexer * TextEditor::getLexer()
{
    return lexer();
}

void TextEditor::setLexerByName(const QString& lexName)
{
    PluginWrapper<Lexers::ILexers> lexers = loadPlugin<Lexers::ILexers>("liblexers.so");
    _lex = lexers->get(lexName, this);
    setLexer(_lex->lexer());
}

void TextEditor::setText(const QString& text)
{
    QsciScintilla::setText(text);
    setModified(false);
}

void TextEditor::onModificationChanged(bool m)
{
    emit undoAvailable(isUndoAvailable());
    _bar->setModified(m);
}

void TextEditor::onSelectionChanged()
{
    //emit copyAvailable(is);
}

void TextEditor::activate()
{
    emit undoAvailable(isUndoAvailable());
    emit redoAvailable(isRedoAvailable());
    emit copyAvailable(selectedText().length() > 0);
    emit modificationChanged(isModified());
}

void TextEditor::undo()
{
    QsciScintilla::undo();
    emit redoAvailable(isRedoAvailable());
}

void TextEditor::redo()
{
    QsciScintilla::redo();
    emit redoAvailable(isRedoAvailable());
}

void TextEditor::copy()
{
    QsciScintilla::copy();
}

void TextEditor::cut()
{
    QsciScintilla::cut();
}

void TextEditor::paste()
{
    QsciScintilla::paste();
}

void TextEditor::del()
{
    QsciScintilla::removeSelectedText();
}

void TextEditor::selectAll()
{
    QsciScintilla::selectAll(true);
}

QString TextEditor::selectedText()
{
    return QsciScintilla::selectedText();
}

void TextEditor::find(const QString& find, bool forward)
{
    int line = -1, index = -1;
    getCursorPosition(&line, &index);
    if (index > 0 && !forward)
        index--;
    findFirst(find, false, false, false, true, forward, line, index);
}

void TextEditor::replace(const QString& findText, const QString& replaceText, bool findNext, bool forward)
{
    QsciScintilla::replace(replaceText);
    if (findNext)
        find(findText, forward);
}

void TextEditor::replaceAll(const QString& findText, const QString& replaceText)
{
    beginUndoAction();
    if (findFirst(findText, false, false, false, false, true, 0, 0)){
        QsciScintilla::replace(replaceText);
        while(findNext()){
            QsciScintilla::replace(replaceText);
        }
    }
    endUndoAction();
}

void TextEditor::uppercase()
{
    QString str = selectedText().toUpper();
    SendScintilla(SCI_REPLACESEL, qPrintable(str));
}

void TextEditor::lowercase()
{
    QString str = selectedText().toLower();
    SendScintilla(SCI_REPLACESEL, qPrintable(str));
}

void TextEditor::capitalize()
{
    QString str = selectedText();
    str = str[0].toUpper()+str.mid(1).toLower();
    SendScintilla(SCI_REPLACESEL, qPrintable(str));
}

void TextEditor::setStatusBar(StatusBar *bar)
{
    _bar = bar;
}

void TextEditor::onCursorPositionChanged(int line, int index)
{
    if (_bar)
        _bar->setCursorPosition(line, index);
}

void TextEditor::setProject(Project *prj)
{
    _project = prj;
}

bool TextEditor::isModified()
{
    return QsciScintilla::isModified();
}

void TextEditor::clearErrors()
{
    markerDeleteAll(_errMarker);
    markerDeleteAll(_warnMarker);
    markerDeleteAll(_convMarker);
    _warnings.clear();
}

void TextEditor::markErrors(int lineNo, const QString & message, int sev)
{
    if (sev & Convection){
        markerAdd(lineNo-1, _convMarker);
    }
    if (sev & Warning){
        markerAdd(lineNo-1, _warnMarker);
    }
    if (sev & Error){
        markerAdd(lineNo-1, _errMarker);
    }
    if (_warnings.contains(lineNo-1))
        _warnings[lineNo-1] += "\n"+message;
    else
        _warnings[lineNo-1] = message;
    //setMarginText(lineNo-1, message, 1);
}

bool TextEditor::event(QEvent *event)
{
    if (event->type() == QEvent::ToolTip) {
        QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event);
        int pos = SendScintilla(QsciScintilla::SCI_POSITIONFROMPOINT, helpEvent->pos().x(), helpEvent->pos().y());
        int line = 0;
        int ind = 0;
        lineIndexFromPosition(pos, &line, &ind);
        if (_warnings.contains(line) && helpEvent->pos().x() < marginWidth(3)){
            QToolTip::showText(helpEvent->globalPos(), _warnings[line]);
        } else {
            QToolTip::hideText();
            event->ignore();
        }
        return true;
    }
    return QsciScintilla::event(event);
}

bool TextEditor::reset(){
    return false;
}

void TextEditor::jumpLine(int lineNo)
{
    setCursorPosition(lineNo-1, 0);
    setFocus();
}

void TextEditor::jumpOffset(int offset)
{
    int line = 0, index = 0;
    lineIndexFromPosition(offset, &line, &index);
    setCursorPosition(line, index);
    setFocus();
}

void TextEditor::allocateId(int &id, unsigned &allocated, int min, int max)
{
    if (id >= 0)
    {
        // Note that we allow existing identifiers to be explicitly redefined.
        if (id > max)
            id = -1;
    }
    else
    {
        unsigned aids = allocated >> min;

        // Find the smallest unallocated identifier.
        for (id = min; id <= max; ++id)
        {
            if ((aids & 1) == 0)
                break;

            aids >>= 1;
        }
    }

    // Allocate the identifier if it is valid.
    if (id >= 0)
        allocated |= (1 << id);
}

void TextEditor::checkIndicatorOld(int &indicatorNumber)
{
    allocateId(indicatorNumber, allocatedIndicatorsOld, INDIC_CONTAINER, INDIC_MAX);
}

int TextEditor::indicatorDefineOld(long style, int indicatorNumber)
{
    checkIndicatorOld(indicatorNumber);

    if (indicatorNumber >= 0)
        SendScintilla(SCI_INDICSETSTYLE, indicatorNumber, style);

    return indicatorNumber;
}

bool TextEditor::indicatorDrawUnderOld(int indicatorNumber) const
{
    if (indicatorNumber < 0 || indicatorNumber >= INDIC_MAX)
        return false;

    return SendScintilla(SCI_INDICGETUNDER, indicatorNumber);
}

void TextEditor::setIndicatorForegroundColorOld(const QColor &col, int indicatorNumber)
{
    if (indicatorNumber <= INDIC_MAX)
    {
        int alpha = col.alpha();

        // We ignore allocatedIndicators to allow any indicators defined
        // elsewhere (e.g. in lexers) to be set.
        if (indicatorNumber < 0)
        {
            for (int i = 0; i <= INDIC_MAX; ++i)
            {
                SendScintilla(SCI_INDICSETFORE, i, col);
                SendScintilla(SCI_INDICSETALPHA, i, alpha);
            }
        }
        else
        {
            SendScintilla(SCI_INDICSETFORE, indicatorNumber, col);
            SendScintilla(SCI_INDICSETALPHA, indicatorNumber, alpha);
        }
    }
}

void TextEditor::setIndicatorDrawUnderOld(bool under, int indicatorNumber)
{
    if (indicatorNumber <= INDIC_MAX)
    {
        // We ignore allocatedIndicators to allow any indicators defined
        // elsewhere (e.g. in lexers) to be set.
        if (indicatorNumber < 0)
        {
            for (int i = 0; i <= INDIC_MAX; ++i)
                SendScintilla(SCI_INDICSETUNDER, i, under);
        }
        else
        {
            SendScintilla(SCI_INDICSETUNDER, indicatorNumber, under);
        }
    }
}

void TextEditor::fillIndicatorRange(int lineFrom, int indexFrom, int lineTo, int indexTo, int indicatorNumber)
{
    if (indicatorNumber <= INDIC_MAX)
    {
        int start = positionFromLineIndex(lineFrom, indexFrom);
        int finish = positionFromLineIndex(lineTo, indexTo);

        // We ignore allocatedIndicators to allow any indicators defined
        // elsewhere (e.g. in lexers) to be set.
        if (indicatorNumber < 0)
        {
            for (int i = 0; i <= INDIC_MAX; ++i)
            {
                SendScintilla(SCI_SETINDICATORCURRENT, i);
                SendScintilla(SCI_INDICATORFILLRANGE, start, finish - start);
            }
        }
        else
        {
            SendScintilla(SCI_SETINDICATORCURRENT, indicatorNumber);
            SendScintilla(SCI_INDICATORFILLRANGE, start, finish - start);
        }
    }
}

}

